#!/bin/sh
# shellcheck shell=dash source=/dev/null
#
# This script mounts USB mass storage devices when they are plugged in
# and unmounts them when they are removed.
# Copyright © 2004, 2005 Martin Dickopp
# Copyright © 2008, 2009, 2010 Rogério Theodoro de Brito
#
# This file is free software; the copyright holder gives unlimited
# permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
#
set -e
exec > /dev/null 2>&1

######################################################################
# Auxiliary functions

# Log a string via the syslog facility.
log()
{
  if [ "$1" != "debug" ] || expr "$VERBOSE" : "[yY]" > /dev/null; then
    logger -p "user.$1" -t "usbmount[$$]" -- "$2"
  fi
}


# Test if the first parameter is in the list given by the second
# parameter.
in_list()
{
  for v in $2; do
    [ "$1" != "$v" ] || return 0
  done
  return 1
}


######################################################################
# Main program

# Default values for configuration variables.
ENABLED=1
MOUNTPOINTS=
FILESYSTEMS=
MOUNTOPTIONS=
FS_MOUNTOPTIONS=
VERBOSE=no

if [ -r /etc/usbmount/usbmount.conf ]; then
  . /etc/usbmount/usbmount.conf
  log debug "loaded usbmount configurations"
fi

if [ "${ENABLED:-1}" -eq 0 ]; then
  log info "usbmount is disabled, see /etc/usbmount/usbmount.conf"
  exit 0
fi

# Per Policy 9.3.2, directories under /var/run have to be created
# after every reboot.
if [ ! -e /var/run/usbmount ]; then
  mkdir -p /var/run/usbmount
  log debug "creating /var/run/usbmount directory"
fi

umask 022

if [ "$1" = add ]; then

  # Acquire lock.
  log debug "trying to acquire lock /var/run/usbmount/.mount.lock"
  lockfile-create --retry 3 /var/run/usbmount/.mount || \
    { log err "cannot acquire lock /var/run/usbmount/.mount.lock"; exit 1; }
  trap '( lockfile-remove /var/run/usbmount/.mount )' 0
  log debug "acquired lock /var/run/usbmount/.mount.lock"

  if ! echo "$ID_FS_USAGE" | grep -E -q "(filesystem|disklabel)"; then
    log info "$DEVNAME does not contain a filesystem or disklabel"
    exit 1
  fi

  # Try to use specifications in /etc/fstab first.
  if grep -E -q "^[[:blank:]]*$DEVNAME" /etc/fstab; then
    log info "executing command: mount $DEVNAME"
    mount "$DEVNAME" || log err "mount by DEVNAME with $DEVNAME wasn't successful; return code $?"

  elif grep -E -q "^[[:blank:]]*UUID=$ID_FS_UUID" /etc/fstab; then
    log info "executing command: mount -U $ID_FS_UUID"
    mount -U "$ID_FS_UUID" || log err "mount by UUID with $ID_FS_UUID wasn't successful; return code $?"

  else
    log debug "$DEVNAME contains filesystem type $ID_FS_TYPE"

    fstype=$ID_FS_TYPE
    # Test if the filesystem type is in the list of filesystem
    # types to mount.
    if in_list "$fstype" "$FILESYSTEMS"; then
      # Search an available mountpoint.
      for v in $MOUNTPOINTS; do
        mkdir -p "$v"
        if [ -d "$v" ] && ! grep -q "^[^ ][^ ]*  *$v " /proc/mounts; then
          mountpoint="$v"
          log debug "mountpoint $mountpoint is available for $DEVNAME"
          break
        fi
      done
      if [ -n "$mountpoint" ]; then
        # Determine mount options.
        options=
        for v in $FS_MOUNTOPTIONS; do
          if expr "$v" : "-fstype=$fstype,."; then
            options="$(echo "$v" | sed 's/^[^,]*,//')"
            break
          fi
        done
        if [ -n "$MOUNTOPTIONS" ]; then
          options="$MOUNTOPTIONS${options:+,$options}"
        fi

        # Mount the filesystem.
        log info "executing command: mount -t$fstype ${options:+-o$options} $DEVNAME $mountpoint"
        if [ "$fstype" = "ntfs" ]; then
          modprobe fuse
          mount.ntfs-3g "${options:+-o$options}" "$DEVNAME" "$mountpoint"
        elif [ "$fstype" = "exfat" ]; then
          modprobe fuse
          mount.exfat "${options:+-o$options}" "$DEVNAME" "$mountpoint"
        else
          mount "-t$fstype" "${options:+-o$options}" "$DEVNAME" "$mountpoint"
        fi

        # Determine vendor and model.
        vendor=
        if [ -r "/sys$DEVPATH/device/vendor" ]; then
          vendor="$(cat "/sys$DEVPATH/device/vendor")"
        elif [ -r "/sys$DEVPATH/../device/vendor" ]; then
          vendor="$(cat "/sys$DEVPATH/../device/vendor")"
        elif [ -r "/sys$DEVPATH/device/../manufacturer" ]; then
          vendor="$(cat "/sys$DEVPATH/device/../manufacturer")"
        elif [ -r "/sys$DEVPATH/../device/../manufacturer" ]; then
          vendor="$(cat "/sys$DEVPATH/../device/../manufacturer")"
        fi
        vendor="$(echo "$vendor" | sed 's/^[[:blank:]]\+//; s/[[:blank:]]\+$//')"

        model=
        if [ -r "/sys$DEVPATH/device/model" ]; then
          model="$(cat "/sys$DEVPATH/device/model")"
        elif [ -r "/sys$DEVPATH/../device/model" ]; then
          model="$(cat "/sys$DEVPATH/../device/model")"
        elif [ -r "/sys$DEVPATH/device/../product" ]; then
          model="$(cat "/sys$DEVPATH/device/../product")"
        elif [ -r "/sys$DEVPATH/../device/../product" ]; then
          model="$(cat "/sys$DEVPATH/../device/../product")"
        fi
        model="$(echo "$model" | sed 's/^[[:blank:]]\+//; s/[[:blank:]]\+$//')"

        # Run hook scripts; ignore errors.
        export UM_DEVICE="$DEVNAME"
        export UM_UUID="$ID_FS_UUID"
        export UM_MOUNTPOINT="$mountpoint"
        export UM_FILESYSTEM="$fstype"
        export UM_MOUNTOPTIONS="$options"
        export UM_VENDOR="$vendor"
        export UM_MODEL="$model"
        export UM_LABEL="$ID_FS_LABEL"
        log info "executing command: run-parts /etc/usbmount/mount.d"
        run-parts /etc/usbmount/mount.d || :
      else
        # No suitable mount point found.
        log warning "no mountpoint found for $DEVNAME"
      exit 1
      fi
    fi
  fi

elif [ "$1" = remove ]; then

  # A block or partition device has been removed.
  # Test if it is mounted.
  # shellcheck disable=SC2034
  while read -r device mountpoint fstype remainder; do
    if [ "$DEVNAME" = "$device" ]; then
      # If the mountpoint and filesystem type are maintained by
      # this script, unmount the filesystem.
      if in_list "$mountpoint" "$MOUNTPOINTS" &&
         in_list "$fstype" "$FILESYSTEMS"; then
        log info "executing command: umount -l $mountpoint"
        umount -l "$mountpoint"

        # Run hook scripts; ignore errors.
        export UM_DEVICE="$DEVNAME"
        export UM_MOUNTPOINT="$mountpoint"
        export UM_FILESYSTEM="$fstype"
        log info "executing command: run-parts /etc/usbmount/umount.d"
        run-parts /etc/usbmount/umount.d || :
      fi
      break
    fi
  done < /proc/mounts
else
  log err "unexpected: action '$1'"
  exit 1
fi

log debug "usbmount execution finished"
